# -*- coding: utf-8 -*-
"""
中性策略框架 | 邢不行 | 2024分享会
author: 邢不行
微信: xbx6660
"""
import sys
import time
from joblib import Parallel, delayed, dump
from Functions import *
from Evaluate import *
from tqdm import tqdm
from Config import *

# 动态读取config里面配置的strategy_name脚本
Strategy = __import__('strategy.%s' % strategy_name, fromlist=('',))
# 从Strategy中获取需要回测的filter_list
filter_list = Strategy.filter_list

# 获取当前环境下的python解释器
python_exec = sys.executable
exec_file_name = '2_选币_单offset.py'


def run(args):
    _select_factor_str = factor_info_to_str(args[0])
    _weight_str = args[1]  # None默认为等权重
    _filter_factor_str = args[2]
    _hold_period = args[3]
    _offset = args[4]
    _select_coin_num_str = args[5]
    _if_use_spot = args[6]
    result_path = args[7]
    _tmp_path = args[8]
    if_save = False
    os.system('%s %s %s %s %s %s %s %s %s %s %s %s' % (python_exec, exec_file_name, _select_factor_str, _weight_str, _filter_factor_str, _hold_period, _offset, _select_coin_num_str, _if_use_spot, if_save, result_path, _tmp_path))
    return


if __name__ == '__main__':
    # =====遍历回测的准备
    # 需要回测的因子
    # factor_class_list = get_file_in_folder(root_path + '/program/factors/', file_type='.py', filters=['__init__'],
    #                                        drop_type=True)
    select_factors = ['BollCountPunish']  # 可以自定义因子进行遍历
    #select_params = [3, 7, 14, 30]  # 设置选币因子的参数。设置前应该保证对应参数数据已经被整理过
    #select_params = [24,50,70,90,110,120,121,122,123,124,125,130,133,140,141,142,143,144,145,146,]
    #select_params = [120, 125, 130, 135, 140, 145, 150, 155, 160, 165, 170, 175, 180, 185, 190, 195, 200]
    '''select_params = [135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150,
151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166,
167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178]'''
    select_params = [24,90,110,120,140,168,180,200,240,320,360,420,480,540,640,720,800,900,1000]
    filter_factors = [] #['涨跌幅max']  # 不支持遍历过滤因子，这里配置过滤因子只是将过滤因子删除掉。默认为策略文件中的过滤
    select_factors = [_ for _ in select_factors if ('风格' not in _) & (_ not in filter_factors)]  # 排除因子分析工具携带的辅助因子
    hold_period = '1H'  # 指定hold_period,设置的周期必需确定有相应数据的生成
    offset = list(range(0, int(hold_period[:-1])))  # 指定offset
    long_select_coin_num = 5  # 指定多头选币数量
    short_select_coin_num = 'long_nums'  # 指定空头选币数量
    if_use_spot = False  # 指定是否使用现货
    result_path = os.path.join(back_test_path + '回测结果汇总_遍历选币参数.csv')

    # 构建几个因子的组合方式(数字越大，越耗时)
    factor_count = 1

    # 是否使用共享内存
    is_shared_data = True

    # 构建选币因子列
    select_factor_list = []
    for factor in select_factors:
        for param in select_params:
            select_factor_list.append(f'{factor}_{param}')

    # 构建过滤因子列
    filter_factor_list = []
    if filter_list:
        for _filter_factor in filter_list:
            filter_factor_list.append(f'{_filter_factor[0]}_{_filter_factor[1]}')
        filter_factor_str = '+'.join(filter_factor_list)
    else:
        filter_factor_str = None
    all_factor_list = list(set(select_factor_list + filter_factor_list))

    # 合并多空选币数量
    select_coin_num = [long_select_coin_num, short_select_coin_num]
    select_coin_num_str = '+'.join(map(str, select_coin_num))

    # 通过itertools.combinations构建因子的排列组合
    factors = list(itertools.combinations(select_factor_list, factor_count))

    # 通过itertools.product构建True，False的全组合
    reverses = list(itertools.product([True, False], repeat=factor_count))

    # 循环遍历因子组合和排序组合，构建完整的回测因子组合(多参数目前默认全是等权)
    factor_para_list = []
    for f in factors:
        for r in reverses:
            factor_dict = {}
            col = []
            for i in range(0, factor_count):
                factor_dict[f[i]] = r[i]
                col.append(f[i].split('_')[0])  # 如果因子名称中带有下划线，会出现统计错误
            # 去除单因子自己与自己的组合
            if len(set(col)) < factor_count:
                continue
            factor_para_list.append(factor_dict)

    # 构建遍历信息
    ergodic_list = []
    # 设置offset遍历逻辑，默认全offset遍历
    for ofs in offset:
        if is_shared_data:
            tmp_path = os.path.join(root_path, 'data/共享内存')
            if not os.path.exists(tmp_path):
                os.makedirs(tmp_path)
            tmp_path = os.path.join(root_path, f'data/共享内存/_tmp_{ofs}.pkl')
            start_time = time.time()
            print('【遍历】正在生成offset为{}的共享内存...'.format(ofs))
            df = read_coin(root_path, hold_period, all_factor_list, if_use_spot, n_jobs, ofs)
            dump(df, tmp_path)
            print('【遍历】offset为{}的共享内存生成完毕：{}'.format(ofs, time.time() - start_time))
        else:
            print('不使用共享内存，直接从本地读取数据')
            tmp_path = None

        for factor_para in factor_para_list:
            ergodic_list.append([factor_para, None, filter_factor_str, hold_period, ofs, select_coin_num_str, if_use_spot, result_path, tmp_path])

    # ergodic_list = ergodic_list[:10]  # 测试使用
    # =====并行或串行，依次调用2号脚本
    multiply_process = True  # 是否并行。在测试的时候可以改成False，实际跑的时候改成True
    if multiply_process:
        df_list = Parallel(n_jobs=n_jobs)(
            delayed(run)(factor_para)
            for factor_para in tqdm(ergodic_list)
        )
    else:
        for factor_para in tqdm(ergodic_list):
            run(factor_para)
